import java.io.File;
import java.io.FileNotFoundException;
import java.util.NoSuchElementException;
import java.util.Scanner;

/**
 * 
 * List containing Activity objects, along with methods to read it all in from a file.
 *
 */
public class ActivityList extends BaphiList {
	
	/**
	 * List containing Activity objects, along with methods to read it all in from a file.
	 * @param fileName - name of Activities file
	 * @param commodities - Commodity list
	 * @param planets - Planet list
	 * @param players - Player list
	 * @param inventory - Inventory list
	 * @param hthOpponents - HandToHand Opponents list
	 * @param stsOpponents - ShipToShip Opponents list
	 * @throws FileNotFoundException
	 * @throws NoSuchElementException
	 */
	public ActivityList(String fileName, CommodityList commodities, PlanetList planets, PlayerList players, 
			InventoryList inventory, OpponentList hthOpponents, OpponentList stsOpponents)
			throws FileNotFoundException, NoSuchElementException
	{
		readActivitiesFromFile(fileName, planets, players);
		// Once we've made one pass through and gotten all the Activities, we can go through again
		// and get the Results.
		readResultsFromFile(fileName, commodities, planets, players, inventory, hthOpponents, stsOpponents);
	}
	
	/**
	 * 
	 * @param planet - Planet index
	 * @param name - Activity unique name
	 * @return index for the Activity that is identified by that planet number and name
	 */
	public int lookup(int planet, String name)
	{
		for (int i = 0; i < this.size(); i++)
			if (this.get(i).getPlanet() == planet  && this.get(i).getName().equalsIgnoreCase(name))
				return i;
		
		return -1;
	}
	
	/**
	 * @param idx - Activity number to return
	 * @return Activity 
	 */
	public Activity get(int idx)
	{
		return (Activity) super.get(idx);
	}
	
	/**
	 * Parses an activity type string
	 * @param typeString
	 * @return enumerated Activity.Type
	 */
	public Activity.Type parseActivityType(String typeString)
	{
		if (typeString.equalsIgnoreCase("Standard"))
			return Activity.Type.Standard;
		else if (typeString.equalsIgnoreCase("Combat"))
			return Activity.Type.Combat;
		else if (typeString.equalsIgnoreCase("EnabledLanding"))
			return Activity.Type.EnabledLanding;
		else if (typeString.equalsIgnoreCase("Landing"))
			return Activity.Type.Landing;
		else if (typeString.equalsIgnoreCase("TakeOff"))
			return Activity.Type.TakeOff;
		else
			return Activity.Type.Error;
	}
	
	/**
	 * Parses an result type string
	 * @param typeString
	 * @return enumerated Result.Type
	 */
	public Result.Type parseResultType(String typeString)
	{
		if (typeString.equalsIgnoreCase("EnableActivity"))
			return Result.Type.EnableActivity;
		else if (typeString.equalsIgnoreCase("DisableActivity"))
			return Result.Type.DisableActivity;
		else if (typeString.equalsIgnoreCase("AddCargo"))
			return Result.Type.AddCargo;
		else if (typeString.equalsIgnoreCase("RemoveCargo"))
			return Result.Type.RemoveCargo;
		else if (typeString.equalsIgnoreCase("AddInventory"))
			return Result.Type.AddInventory;
		else if (typeString.equalsIgnoreCase("RemoveInventory"))
			return Result.Type.RemoveInventory;
		else if (typeString.equalsIgnoreCase("CargoMarket"))
			return Result.Type.CargoMarket;
		else if (typeString.equalsIgnoreCase("ExoticMarket"))
			return Result.Type.ExoticMarket;
		else if (typeString.equalsIgnoreCase("ItemMarket"))
			return Result.Type.ItemMarket;
		else if (typeString.equalsIgnoreCase("DroneMarket"))
			return Result.Type.DroneMarket;
		else if (typeString.equalsIgnoreCase("CargoBayExpansion"))
			return Result.Type.CargoBayExpansion;
		else if (typeString.equalsIgnoreCase("PhaseLoss"))
			return Result.Type.PhaseLoss;
		else if (typeString.equalsIgnoreCase("Reading"))
			return Result.Type.Reading;
		else if (typeString.equalsIgnoreCase("Combat"))
			return Result.Type.Combat;
		else
			return Result.Type.Error;			
	}
	
	/**
	 * Opens the Activities file and populates the Activity list
	 * @param fileName - string containing the file path
	 * @param planets - Planet list
	 * @param players - Player list
	 * @throws FileNotFoundException
	 * @throws NoSuchElementException
	 */
	private void readActivitiesFromFile(String fileName, PlanetList planets, PlayerList players) 
		throws FileNotFoundException, NoSuchElementException
	{
		File file = new File(fileName);
		Scanner scan;
		String planetName = null, activityName;
		int planetNum;
		Activity.Type activityType;
		
		scan = new Scanner(file);
		scan.useDelimiter(",|\n");
		
		while(scan.hasNext())
		{	
			// If this is the first thing read or we just threw out a blank line, we need to read a planet name.
			if (planetName == null)
				planetName = scan.next();
			
			// Blank line? Throw it out and try again.
			if (planetName.equals(""))
			{
				planetName = null;
				continue;
			}
			
			planetNum = planets.lookup(planetName);
			
			if (planetNum == -1)
			{
				throw new java.util.NoSuchElementException(fileName + " " + "Unknown Planet: " + planetName);
			}
			
			// What kind of Activity is it?
			activityType = parseActivityType(scan.next());
			activityName = scan.next();

			Activity newActivity;

			if (activityType == Activity.Type.Combat) // Use Combat constructor
				newActivity = new Activity(activityName, planetNum, players.size());
			else
			{
				String description = scan.next();
				int phaseCost = scan.nextInt();
				int reading = scan.nextInt();
				
				// Use non-combat constructor
				newActivity = new Activity(activityName, planets.lookup(planetName), activityType, description, phaseCost, reading, players.size());
				if (activityType == Activity.Type.EnabledLanding)
				{
					// This activity will initially be enabled for all players. Only one can be enabled at any given time.
					newActivity.setType(Activity.Type.Landing);
					newActivity.enableForAll();
				}
			}
			
			this.add(newActivity);
			
			// Throw away any Result lines; we're holding off on dealing with those until we have all of our activities loaded.
			planetName = skipAllResults(scan);

		}
		
		scan.close();
	}
	
	/**
	 * Throw out Result lines
	 * @param scan
	 * @return name of next planet in the file
	 */
	private String skipAllResults(Scanner scan)
	{
		String resultTypeStr = null;

		while(scan.hasNext())
		{
			resultTypeStr = scan.next();
			if (resultTypeStr.equals("")) // Throw out blank lines
				continue;
			
			// If the next thing isn't a recognized result type, it must be a planet. New activity.
			if (parseResultType(resultTypeStr) == Result.Type.Error) 
				break;
			else
				scan.nextLine(); // Throw away the rest of the line
		}
		
		return resultTypeStr;
	}
	
	/**
	 * Read in all the Results and add them to their owning Activities
	 * @param fileName - Activities file path string
	 * @param commodities - Commodity list
	 * @param planets - Planet list
	 * @param players - Player list
	 * @param inventory - Inventory list
	 * @param hthOpponents - HandToHand Opponents list
	 * @param stsOpponents - ShipToShip Opponents list
	 * @throws FileNotFoundException
	 * @throws NoSuchElementException
	 */
	private void readResultsFromFile(String fileName, CommodityList commodities, PlanetList planets, PlayerList players, 
			InventoryList inventory, OpponentList hthOpponents, OpponentList stsOpponents)
			throws FileNotFoundException, NoSuchElementException
	{
		File file = new File(fileName);
		Scanner scan;
		String lineStartStr = null;
		Activity currentActivity = null;
		int activityNum = 0;
		
		scan = new Scanner(file);
		scan.useDelimiter(",|\n");
		
		while(scan.hasNext())
		{	
			lineStartStr = scan.next();
			
			// Throw away blank lines
			if (lineStartStr.equals(""))
			{
				scan.nextLine();
				continue;
			}
			
			// We found another Activity. Load it up, bump the Activity index, and move to the next line.
			if (planets.lookup(lineStartStr) != -1)
			{
				currentActivity = this.get(activityNum++);
				scan.nextLine();
				continue;
			}

			// Got a Result. Figure out what it is and add it to the current Activity.
			parseResultLine(currentActivity, lineStartStr, scan, commodities, planets, players, 
					inventory, hthOpponents, stsOpponents);
		}
		
		scan.close();
	}
	
	/**
	 * Take a Result line and parse it in any of about a zillion different ways, depending on the ResultType. Then add it to currentActivity's Result list.
	 * @param currentActivity - the Activity we are adding Results to
	 * @param resultTypeStr - Text representation of the result type
	 * @param scan
	 * @param commodities - Commodity list
	 * @param planets - Planet list
	 * @param players - Player list
	 * @param inventory - Inventory list
	 * @param hthOpponents - HandToHand Opponents list
	 * @param stsOpponents - ShipToShip Opponents list
	 * @throws NoSuchElementException
	 */
	private void parseResultLine(Activity currentActivity, String resultTypeStr, Scanner scan, CommodityList commodities,
			PlanetList planets, PlayerList players, InventoryList inventory, OpponentList hthOpponents, OpponentList stsOpponents)
			throws NoSuchElementException
	{
		int intList[];
		CargoList cargoList;
		int cargoIdx;
		Scanner scanStr;
		String lineStr = scan.nextLine();
		
		scanStr = new Scanner(lineStr);	
		scanStr.useDelimiter(",|\n");
		
		// OK, here we go. Figure out what kind of Result it is and parse it appropriately.
		switch(parseResultType(resultTypeStr))
		{
		case EnableActivity:
			intList = readActivityList(lineStr, planets);
			if (intList.length == 0)
				throw new java.util.NoSuchElementException("Empty EnableActivity!");
			currentActivity.addResult(new EnableActivity(intList));
			break;
		case DisableActivity:
			intList = readActivityList(lineStr, planets);
			if (intList.length == 0)
				throw new java.util.NoSuchElementException("Empty DisableActivity!");
			currentActivity.addResult(new DisableActivity(intList));
			break;
		case AddCargo:
			cargoList = readCargoList(lineStr, commodities);
			if (cargoList.size() == 0)
				throw new java.util.NoSuchElementException("Empty AddCargo!");
			currentActivity.addResult(new AddCargo(cargoList));
			break;
		case RemoveCargo:
			RemoveCargo removeCargo = readRemoveCargo(lineStr);
			if (removeCargo == null)
				throw new java.util.NoSuchElementException("Empty RemoveCargo!");
			currentActivity.addResult(removeCargo);
			break;
		case AddInventory:
			intList = readInventoryList(lineStr, inventory);
			if (intList.length == 0)
				throw new java.util.NoSuchElementException("Empty AddInventory!");
			currentActivity.addResult(new AddInventory(intList));
			break;
		case RemoveInventory:
			intList = readInventoryList(lineStr, inventory);
			if (intList.length == 0)
				throw new java.util.NoSuchElementException("Empty RemoveInventory!");
			currentActivity.addResult(new RemoveInventory(intList));
			break;
		case CargoMarket:
			cargoIdx = commodities.lookup(scanStr.next());
			if (cargoIdx == -1)
				throw new java.util.NoSuchElementException("CargoMarket: unknown cargo for sale");
			cargoList = readCargoList(scanStr.nextLine(), commodities);
			if (cargoList.size() == 0)
				throw new java.util.NoSuchElementException("Empty CargoMarket!");
			currentActivity.addResult(new CargoMarket(cargoIdx, cargoList));
			break;
		case ExoticMarket:
			cargoIdx = commodities.lookup(scanStr.next());
			if (cargoIdx == -1)
				throw new java.util.NoSuchElementException("ExoticMarket: unknown cargo for sale");
			cargoList = readCargoList(scanStr.nextLine(), commodities);
			if (cargoList.size() == 0)
				throw new java.util.NoSuchElementException("Empty ExoticMarket!");
			currentActivity.addResult(new ExoticMarket(cargoIdx, cargoList));
			break;
		case ItemMarket:
			MarketItemList marketItemList = readMarketItemList(lineStr, commodities, inventory);
			if (marketItemList.size() == 0)
				throw new java.util.NoSuchElementException("Empty ItemMarket!");
			currentActivity.addResult(new ItemMarket(marketItemList));
			break;
		case DroneMarket:
			int droneCapacity = scanStr.nextInt();
			cargoList = readCargoList(scanStr.nextLine(), commodities);
			if (cargoList.size() == 0)
				throw new java.util.NoSuchElementException("Empty DroneMarket!");
			currentActivity.addResult(new DroneMarket(droneCapacity, cargoList));
			break;
		case CargoBayExpansion:
			cargoList = readCargoList(lineStr, commodities);
			if (cargoList.size() == 0)
				throw new java.util.NoSuchElementException("Empty CargoBayExpansion!");
			currentActivity.addResult(new AddCargo(cargoList));
			break;
		case PhaseLoss:
			currentActivity.addResult(new PhaseLoss(scanStr.nextInt()));
			break;
		case Reading:
			currentActivity.addResult(new Reading(scanStr.nextInt()));
			break;
		case Combat:
			currentActivity.addResult(readCombat(lineStr, hthOpponents, stsOpponents));
			break;
		//TODO Parse Special CGM interactions
		case Error:		
			throw new java.util.NoSuchElementException("Unknown Result Type: " + resultTypeStr);
		}		
	}
	
	/**
	 * Read a list of Activity names in a Result
	 * @param lineStr - input string
	 * @param planets - Planet list
	 * @return array of Activity numbers
	 * @throws NoSuchElementException
	 */
	private int[] readActivityList(String lineStr, PlanetList planets) throws NoSuchElementException
	{
		int activities[];
		int currentActivity = 0;
		String planetName;
		int planetNumber;
		String activityName;
		int activityNumber;
		
		Scanner scan = new Scanner(lineStr);
		scan.useDelimiter(",|\n");
		
		planetName = scan.next();
		planetNumber = planets.lookup(planetName);
		if (planetNumber == -1)
			throw new java.util.NoSuchElementException("Unknown Planet: " + planetName);

		// First pass: count the number of activities so we know how big to make the array
		while (scan.hasNext())
		{
			activityName = scan.next();
			currentActivity++;
		}
		
		scan.close();
		
		activities = new int[currentActivity];
		currentActivity = 0;

		scan = new Scanner(lineStr);
		scan.useDelimiter(",|\n");
		
		scan.next(); // Throw away the planet name; we already know it.
		
		// Second pass: look 'em up and write 'em down
		while (scan.hasNext())
		{
			activityName = scan.next();
			
			activityNumber = this.lookup(planetNumber, activityName);
			if (activityNumber == -1)
				throw new java.util.NoSuchElementException("Unknown Activity: " + planetName + "," + activityName);

			activities[currentActivity++] = activityNumber;
		}
		
		scan.close();
		
		return activities;
	}
	
	/**
	 * Read a list of Cargo in a Result
	 * @param lineStr - input string
	 * @param commodities - Commodity list
	 * @return Cargo list
	 * @throws NoSuchElementException
	 */
	private CargoList readCargoList(String lineStr, CommodityList commodities) throws NoSuchElementException
	{
		CargoList cargoList = new CargoList();
		String cargoName;
		int cargoQuantity, cargoIdx;
		
		Scanner scan = new Scanner(lineStr);
		scan.useDelimiter(",|\n");
	
		while (scan.hasNext())
		{
			cargoQuantity = scan.nextInt();
			cargoName = scan.next();
			cargoIdx = commodities.lookup(cargoName);
			if (cargoIdx == -1)
				throw new java.util.NoSuchElementException("Unknown Commodity: " + cargoName);
			
			cargoList.add(new Cargo(cargoIdx, cargoQuantity));
		}
		
		scan.close();
				
		return cargoList;
	}

	/**
	 * Read Random/Chosen string and a number
	 * @param lineStr - input string
	 * @return RemoveCargo result object
	 * @throws NoSuchElementException
	 */
	private RemoveCargo readRemoveCargo(String lineStr) throws NoSuchElementException
	{
		RemoveCargo removeCargo;
		String randomChosenStr;
		int quantity;
		Scanner scan = new Scanner(lineStr);
		scan.useDelimiter(",|\n");

		randomChosenStr = scan.next();
		quantity = scan.nextInt();
		if (randomChosenStr.equalsIgnoreCase("Random"))
			removeCargo = new RemoveCargo(true, quantity);
		else if (randomChosenStr.equalsIgnoreCase("Chosen"))
			removeCargo = new RemoveCargo(false, quantity);
		else
			throw new java.util.NoSuchElementException("Random or Chosen expected, but read " + randomChosenStr);

		return removeCargo;
	}

	/**
	 * Read a list of Inventory in a Result
	 * @param lineStr - input string
	 * @param inventory - Inventory list
	 * @return array of Inventory numbers
	 * @throws NoSuchElementException
	 */
	private int[] readInventoryList(String lineStr, InventoryList inventory) throws NoSuchElementException
	{
		int inventoryItems[];
		int currentInventory = 0;
		String inventoryName;
		int inventoryNumber;
		
		Scanner scan = new Scanner(lineStr);
		scan.useDelimiter(",|\n");
		
		// First pass: count the inventory items so we can allocate the array
		while (scan.hasNext())
		{
			scan.next();			
			currentInventory++;
		}
		
		scan.close();
		
		inventoryItems = new int[currentInventory];
		currentInventory = 0;

		scan = new Scanner(lineStr);
		scan.useDelimiter(",|\n");
		
		// Second pass: look 'em up and write 'em down
		while (scan.hasNext())
		{
			inventoryName = scan.next();
			
			inventoryNumber = inventory.lookup(inventoryName);
			if (inventoryNumber == -1)
				throw new java.util.NoSuchElementException("Unknown Inventory: " + inventoryName);

			inventoryItems[currentInventory++] = inventoryNumber;
		}
		
		scan.close();
		
		return inventoryItems;
	}
	
	/**
	 * Read a list of Inventory items and their Commodity costs
	 * @param lineStr - input string
	 * @param commodities - Commodity list
	 * @param inventory - Inventory list
	 * @return List of Inventory and Commodities
	 * @throws NoSuchElementException
	 */
	private MarketItemList readMarketItemList(String lineStr, CommodityList commodities, InventoryList inventory)
		throws NoSuchElementException
	{
		CargoList cargoList;
		String itemName, cargoName;
		int itemNumber, cargoIdx, cargoQuantity;

		Scanner scan = new Scanner(lineStr);
		scan.useDelimiter(",|\n");

		MarketItemList marketItemList = new MarketItemList();
		
		while (scan.hasNext())
		{
			itemName = scan.next();
			itemNumber = inventory.lookup(itemName);
			if (itemNumber == -1)
				throw new java.util.NoSuchElementException("ItemMarket: Unknown item: " + itemName);
			
			cargoList = new CargoList();
			
			while (scan.hasNextInt())
			{
				cargoQuantity = scan.nextInt();
				cargoName = scan.next();
				cargoIdx = commodities.lookup(cargoName);
				if (cargoIdx == -1)
					throw new java.util.NoSuchElementException("ItemMarket: Unknown commodity: " + cargoName);

				cargoList.add(new Cargo(cargoIdx, cargoQuantity));
			}
			
			marketItemList.add(new MarketItem(itemNumber, cargoList));
		}
		
		scan.close();
				
		return marketItemList;
	}
	
	/**
	 * Read a Combat result
	 * @param lineStr - input string
	 * @param hthOpponents - List of HandToHand Opponents
	 * @param stsOpponents - List of ShipToShip Opponents
	 * @return Combat result object
	 * @throws NoSuchElementException
	 */
	Combat readCombat(String lineStr, OpponentList hthOpponents, OpponentList stsOpponents)
		throws NoSuchElementException
	{
		Inventory.Mounting combatType;
		int opponentIdx, winActivityIdx, loseActivityIdx, drawActivityIdx;
		String string;
		
		Scanner scan = new Scanner(lineStr);
		scan.useDelimiter(",|\n");
		
		// ShipToShip or HandToHand?
		string = scan.next();
		if (string.equalsIgnoreCase("Personal"))
			combatType = Inventory.Mounting.Personal;
		else if (string.equalsIgnoreCase("Ship"))
			combatType = Inventory.Mounting.Ship;
		else
			throw new java.util.NoSuchElementException("Combat: Unknown type: " + string);

		string = scan.next();
		// Look up the opponent in the appropriate list
		if (combatType == Inventory.Mounting.Personal)
			opponentIdx = hthOpponents.lookup(string);
		else
			opponentIdx = stsOpponents.lookup(string);

		// Winning result
		string = scan.next();
		winActivityIdx = this.lookup(string);
		if (winActivityIdx == -1)
			throw new java.util.NoSuchElementException("Combat: Unknown winning activity name: " + string);

		// Losing result
		string = scan.next();
		loseActivityIdx = this.lookup(string);
		if (loseActivityIdx == -1)
			throw new java.util.NoSuchElementException("Combat: Unknown losing activity name: " + string);

		// Draw result
		string = scan.next();
		drawActivityIdx = this.lookup(string);
		if (drawActivityIdx == -1)
			throw new java.util.NoSuchElementException("Combat: Unknown draw activity name: " + string);

		return new Combat(combatType, opponentIdx, winActivityIdx, loseActivityIdx, drawActivityIdx);
	}
}
